package echo.netty.param;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;

import java.net.InetSocketAddress;

/**
 * @author： zhangnan
 * @date： 2025/5/15 14:19
 * @description： netty 参数优化
 * @modifiedBy：
 * @version: 1.0
 */
public class NettyParamServer {


    /**
     * 参数都在 ChannelOption 中
     *
     * 服务端参数
     *  new ServerBootstrap().option();  // 是给 ServerSocketChannel 配置参数
     *  new ServerBootstrap().childOption();  // 给 SocketChannel 配置参数
     *  参数1：SO_BACKLOG
     *      nio中通过 bind(8080,100);来设置，100即是backlog的大小
     *      netty中通过：.option(ChannelOption。SO_BACKLOG,2) // backlog大小是2
     *      TCP建立连接过程
     *          1：第一次握手，client 发送 SYN 到 server，状态修改为 SYN_SEND,server收到，状态改变为 SYN_REVD,并将该请求放入 sync queue(半连接队列) 队列；
     *          2：第二次握手，server 回复 SYN+ACK 给 client，client收到，状态改变为 ESTABLISHED，并发送 ACK 给 server；
     *          3：第三次握手，server 收到 ACK，状态改变为 ESTABLISHED，将该请求从 sync queue 放入 accept queue(全连接队列 )。
     *          4：accept 发生在三次握手之后
     *     在linux 2.2 之前，backlog 大小包括了两个队列的大小，在2.2之后，分别用下面两个参数来控制
     *          1：sync queue - 半连接队列
     *              大小通过/proc/sys/net/ipv4/tcp_max_syn_backlog指定，在 syncookies 启用的情况下，逻辑上没有最大值限制，这个设置便被忽略
     *          2：accept queue - 全连接队列
     *              大小通过 /proc/sys/net/core/somaxconn 指定，在使用 listen 函数时，内核会根据传入的 backlog 参数与系统参数，取二者的较小值（例如：netty中和linux中均设置了backlog的大小，较小的那个就会生效）;
     *              如果 accpet queue 队列满了，server 将发送一个拒绝连接的错误信息到 client,(java中：Connection Exception)
     *
     *  参数2：TCP_NODELAY
     *      使用 new ServerBootstrap().childOption() 设置，netty默认设置为false 即 开启了nagle(积攒消息到一定的量后才发出去) 算法
     *
     *  参数3：SO_SNDBUF & SO_RCVBUF
     *      注意：这俩个参数，netty会自动设置，最好不要手动设置
     *      SO_SNDBUF 属于 SocketChannal 参数
     *      SO_RCVBUF 既可用于 SocketChannal 参数，也可以用于 ServerSocketChannal参数(建议设置到ServerSocketChannal上)
     *
     *
     * 客户端参数
     *  通过 new Bootstrap().option() 方法配置参数
     *  1 CONNECT_TIMEOUT_MILLIS
     *      属于 SocketChannal 参数
     *      用在客户端建立连接时，如果在指定毫秒内无法连接，会抛出 timeout异常
     *
     *  1.1 了解，阻塞IO中
     *      SO_TIMEOUT 主要用在阻塞IO，阻塞 IO 中 accept，read 等都是无限等待的，如果不希望永远阻塞，使用它调整超时时间
     *
     *  ALLOCATOR
     *      属于 SocketChannel 参数，用来分配 ByteBuf，ctx.alloc()
     *
     *  RCVBUF_ALLOCATOR
     *      属于 SocketChannel 参数，控制 netty 接收缓冲区大小，负责入站数据的分配，决定入站缓冲区的大小(并可动  态调整)，统一采用 direct 直接内存，具体池化还是非池化由 allocator 决定
     *
     * 操作系统参数
     *  ulimit -n x：属于操作系统参数，一个进程允许同时打开文件描述符(FD)的数量，例如：ulimit -n 256
     *
     */

    public static void main(String[] args) {
        TestParamServer();

        TestParamClient();

    }

    private static void TestParamClient() {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        }
                    })
                    .connect(new InetSocketAddress("localhost",8080));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    private static void TestParamServer() {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup workers = new NioEventLoopGroup(2);
        try {
            new ServerBootstrap()
                    .channel(NioServerSocketChannel.class)
                    .group(boss,workers)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,300) // 300毫秒后没有连接上服务端就会抛出异常
                    .option(ChannelOption.SO_BACKLOG,2)
                    .childOption(ChannelOption.AUTO_READ,false)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new LoggingHandler(LogLevel.DEBUG));
                        }
                    })
                    .bind(8080);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            workers.shutdownGracefully();
        }
    }



}
